package com.heima.schedule.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.common.constants.ScheduleConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.schedule.dtos.TaskDto;
import com.heima.model.schedule.pojos.Taskinfo;
import com.heima.model.schedule.pojos.TaskinfoLogs;
import com.heima.schedule.mapper.TaskinfoLogsMapper;
import com.heima.schedule.mapper.TaskinfoMapper;
import com.heima.schedule.service.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Calendar;
import java.util.Date;
import java.util.Set;

@Service
@Slf4j
@Transactional
public class TaskServiceImpl2  {
    @Autowired
    private TaskinfoMapper taskinfoMapper;
    @Autowired
    private TaskinfoLogsMapper taskinfoLogsMapper;
    @Autowired
    private CacheService cacheService;

    /**
     * 添加任务
     *
     * @param dto
     * @return
     */
    public Long addTask(TaskDto dto) {
        //1、准备参数（生成id需要返回）
        long taskId = IdWorker.getId();
        dto.setTaskId(taskId);
        //2、存入数据库
        boolean res = addTaskToDB(dto);
        //3、将符合条件的任务存入redis
        if (res) {
            addTaskToRedis(dto);
        }
        return taskId;
    }

    /**
     * 存入redis
     *
     * @param dto
     */
    private void addTaskToRedis(TaskDto dto) {
        //1、执行时间 <= 当前系统时间毫秒值 放入list中
        //1.1、准备key
        String key = dto.getTaskType() + "_" + dto.getPriority();
        //1.2、准备一个当前系统时间 + 5 分钟
        Calendar calendar = Calendar.getInstance();//拿到当前系统毫秒值
        calendar.add(Calendar.MINUTE, 5);//当前时间 + 5 分钟
        if (dto.getExecuteTime() <= System.currentTimeMillis()) {
            // 1.2、转成json字符串存入list
            cacheService.lLeftPush(ScheduleConstants.TOPIC + key, JSON.toJSONString(dto));
            //2、执行时间 > 当前系统时间毫秒值，放入zSet中 （无序，不重复）
        } else if (dto.getExecuteTime() <= calendar.getTimeInMillis()) {
            cacheService.zAdd(ScheduleConstants.FUTURE + key, JSON.toJSONString(dto), dto.getExecuteTime());
        }
    }

    /**
     * 添加到数据库
     *
     * @param dto
     * @return
     */
    private boolean addTaskToDB(TaskDto dto) {
        //0、信号位
        boolean flag = false;
        try {
            //1.1、准备参数
            Taskinfo taskinfo = new Taskinfo();
            //1.2、拷贝参数
            BeanUtils.copyProperties(dto, taskinfo);
            //1.3、时间的数据类型不一样，不能拷贝
            taskinfo.setExecuteTime(new Date(dto.getExecuteTime()));
            //2、添加到taskInfo表（更新到redis后会删除）
            taskinfoMapper.insert(taskinfo);
            //3、添加到taskInfoLogs表（备份）
            //3.1、准备参数
            TaskinfoLogs taskinfoLogs = new TaskinfoLogs();
            //3.2、属性拷贝
            BeanUtils.copyProperties(taskinfo, taskinfoLogs);
            //3.3、完善属性
            taskinfoLogs.setVersion(1);
            taskinfoLogs.setStatus(ScheduleConstants.SCHEDULED);//初始化
            //3.3、保存到数据表
            taskinfoLogsMapper.insert(taskinfoLogs);
            //4、改变信号位
            flag = true;
        } catch (BeansException e) {
            e.printStackTrace();
        }
        //5、返回信号
        return flag;
    }

    /**
     * 拉取任务（消费）
     *
     * @param taskType
     * @param priority
     * @return
     */
    public TaskDto popTask(Integer taskType, Integer priority) {
        //1、准备key
        String key = ScheduleConstants.TOPIC + taskType + "_" + priority;
        //2、读取redis里面的list数据 （从右边取）
        String json = cacheService.lRightPop(key);
        //3、将json转为TaskDto对象
        TaskDto taskDto = JSON.parseObject(json, TaskDto.class);
        //4、删除taskInfo数据
        taskinfoMapper.deleteById(taskDto.getTaskId());
        //5、修改taskInfoLog表
        taskinfoLogsMapper.update(null, Wrappers.<TaskinfoLogs>lambdaUpdate()
                .eq(TaskinfoLogs::getTaskId, taskDto.getTaskId())
                .set(TaskinfoLogs::getStatus, ScheduleConstants.EXECUTED));
        //6、返回任务对象
        return taskDto;
    }

    /**
     * 定时刷新 (每分钟) redis里的 zset里面的数据到list
     */
  //  @Scheduled(cron = "0 */1 * * * ?")
    public void refresh() {
        log.info("定时刷新任务开启，将zset里面符合条件的数据刷到list中。。。");
        //1、先取出zset所有符合数据的集合 其实也就一种
        Set<String> zSetKeys = cacheService.scan(ScheduleConstants.FUTURE + "*");
        //1.1、代码健壮性
        if (zSetKeys != null && zSetKeys.size() > 0) {
            //2、遍历
            for (String key : zSetKeys) {
                //2.1、取出需要被消费的任务 即任务执行时间毫秒值 小于当前时间毫秒值
                Set<String> taskDtos = cacheService.zRangeByScore(key, 0, System.currentTimeMillis());
                //2.2、准备 topicKey
                String topicKey = ScheduleConstants.TOPIC + key.split(ScheduleConstants.FUTURE)[1];
                //代码健壮性 非空校验
                if (taskDtos != null && taskDtos.size() > 0) {
                    //2.3、将符合条件的任务转到list中去 其中要做两件事 但是已经封装好方法了
                    //--1、根据futureKey（zSetKey）找到对应的zSet，将里卖弄的task删除
                    //--2、根据topicKey找到对饮的list，将里面对应的task添加
                    cacheService.refreshWithPipeline(key, topicKey, taskDtos);
                }
            }
        }
    }
}
